{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "view-in-github"
   },
   "source": [
    "<a href=\"https://colab.research.google.com/github/jermwatt/machine_learning_refined/blob/main/notes/3_First_order_methods/A_4_Advanced.ipynb\" target=\"_parent\"><img src=\"https://colab.research.google.com/assets/colab-badge.svg\" alt=\"Open In Colab\"/></a>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "AQ7-FxHlLhTl"
   },
   "source": [
    "## Appendix A. Advanced First- and Second-Order Optimization Methods"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "This notebook contains interactive content from an early draft of the university textbook <a href=\"https://github.com/neonwatty/machine-learning-refined/tree/main\">\n",
    "Machine Learning Refined (2nd edition) </a>.\n",
    "\n",
    "The final draft significantly expands on this content and is available for <a href=\"https://github.com/neonwatty/machine-learning-refined/tree/main/chapter_pdfs\"> download as a PDF here</a>."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "mpA0SBiNLhTn"
   },
   "source": [
    "# A.4 Advanced Gradient-Based Methods"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "fW-ngl2bLhTo"
   },
   "source": [
    "In [Section 3.7](https://jermwatt.github.io/machine_learning_refined/notes/3_First_order_methods/3_7_Problems.html) we discussed two fundamental problems with the negative gradient insofar as it is used as a descent direction for local optimization: the zig-zagging and slow-crawling behaviors.  In the Sections that then followed we detailed fundamental solutions to each of these problems independently, momentum and gradient normalized descent.  These two fundamental problems can certainly occur together in practice, particularly with the sort of functions we minimize in machine learning (particularly those involving *neural networks*).  They both occur with functions that have flat long narrow valleys that provoke zig-zagging of gradient descent steps due to the shape of their contours, and slow crawling of the steps due to the area's flatness. "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "I8OA5OwWLhTo"
   },
   "source": [
    "Because of this many advanced first order gradient based methods have been developed in the machine learning community that essentially combine the momentum and normalized gradient ideas in various interesting ways.  In this Section we detail several of these popular methods including RMSprop, and Adam, highlighting their connection to the two fundamental solutions methods discussed in the prior two Sections."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "hr1BHNNKLhTo"
   },
   "source": [
    "##  Combining momentum with normalized gradient descent"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "fUrlafFnLhTo"
   },
   "source": [
    "In [Section 3.7](https://jermwatt.github.io/machine_learning_refined/notes/3_First_order_methods/3_7_Problems.html) we described the notion of *momentum accelerated gradient descent*, and how it is a natural remedy for the *zig-zagging* problem the standard gradient descent algorithm suffers from when run along *long narrow valleys*.  As we the momentum acceleration descent direction $\\mathbf{d}^{k-1}$ is simply an *exponential average* of gradient descent directions taking the form\n",
    "\n",
    "\\begin{equation}\n",
    "\\mathbf{d}^{k-1} = \\beta \\, \\mathbf{d}^{k-2}  - \\left(1 - \\beta\\right)\\nabla g\\left(\\mathbf{w}^{k-1}\\right) \\\\\n",
    "\\mathbf{w}^{\\,k} = \\mathbf{w}^{\\,k-1} + \\alpha \\, \\mathbf{d}^{k-1} \\,\\,\\,\\,\\,\\,\\,\\,\\,\\,\\,\\,\\,     \\,\\,\\,\\,\\,\\,\\,\\,\\,\\,\\,\\,\\,      \\,\\,\\,\n",
    "\\end{equation}\n",
    "\n",
    "where $\\beta \\in \\left[0,1 \\right]$ is typically set at a value of $\\beta = 0.8$ or higher."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "BXYXKu8ELhTp"
   },
   "source": [
    "Then in [Section 3.9](https://jermwatt.github.io/machine_learning_refined/notes/3_First_order_methods/3_9_Normalized.html) we saw how *normalizing the gradient descent direction componentwise* helps deal with the problem standard gradient descent has when traversering *flat regions* of a function.  We saw there how a component-normalized gradient descent step takes the form (for the $j^{th}$ component of $\\mathbf{w}$)\n",
    "\n",
    "\\begin{equation}\n",
    "w_j^k = w_j^{k-1} - \\alpha \\, \\frac{\\frac{\\partial}{\\partial w_j}g\\left(\\mathbf{w}^{k-1}\\right)}{{\\sqrt{\\left(\\frac{\\partial}{\\partial w_j}g\\left(\\mathbf{w}\\right)\\right)^2}}}\n",
    "\\end{equation}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "YeuHHihALhTp"
   },
   "source": [
    "where in practice of course a small fixed value $\\epsilon > 0$ is often added to the denominator on the right hand side to avoid division by zero."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "H5PyKnqlLhTp"
   },
   "source": [
    "Knowing that these two additions to the standard gradient descent step help solve two fundamental problems associated with gradient descent, it is natural to then try to *combine them* to leverage both enhancements.  There are several ways one might think to combine these two ideas.  For example, one could momentum acclerate a componentwise-normalized direction or - in other words - replace the gradient descent direction in the exponential average in equation (1) with its component-normalized version shown in equation (2). "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "bG3M2apvLhTq"
   },
   "source": [
    "Another way of combining the two ideas would be to *component-normalize the exponential average descent direction computed in momentum-accelerated gradient descent*.  That is, compute the exponential average direction in the top line of equation (1) and then normalize *it* (instead of the raw gradient descent direction) as shown in equation (2)."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "sBBv9v8nLhTq"
   },
   "source": [
    "Doing this - and writing out the update for only the $j^{th}$ component of the resulting step - we have\n",
    "\n",
    "\\begin{equation}\n",
    "d^{k-1}_j = \\beta \\, d^{k-2}_j  - \\left(1 - \\beta\\right)\\frac{\\partial}{\\partial w_j}g\\left(\\mathbf{w}^{k-1}\\right) \\\\\n",
    "d^{k-1}_j \\longleftarrow  \\frac{d^{k-1}_j }{\\sqrt{\\left(d^{k-1}_j \\right)^2}}\\\\\n",
    "\\end{equation}\n",
    "\n",
    "where in practice of course a small $\\epsilon > 0$ (like e.g., $\\epsilon = 10^{-8}$) is added to the denominator to avoid division by zero. "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "J3mKSgl9LhTq"
   },
   "source": [
    "With a full direction $\\mathbf{d}^{k-1}$ commputed in this way we can then take a descent step\n",
    "\n",
    "\\begin{equation}\n",
    "\\mathbf{w}^{\\,k} = \\mathbf{w}^{\\,k-1} + \\alpha \\, \\mathbf{d}^{k-1}. \\end{equation}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "g9zaW17uLhTr"
   },
   "source": [
    "Many popular first order steps used to tune machine learning models employing deep neural networks combine momentum and normalized gradient descent in this sort of way.  Below we list a few examples including the popular *Adam* and *RMSprop* first order steps."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "KAHYu4g1LhTr"
   },
   "source": [
    "#### <span style=\"color:#a50e3e;\">Example. 1 </span>  Adaptive Moment Estimation (Adam) "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "eHtZx2gtLhTr"
   },
   "source": [
    "Adaptive Moment Estimation (Adam) is a componentwise-normalized gradient step employing independently calculated exponential averages for both the descent direction *and* magnitude.  That is, we compute $j^{th}$ coordinate of the updated descent direction by first computing the exponential average of the gradient descent direction $d_j^{k}$  squared magnitude $h_j^{k}$ separately along this coordinate as \n",
    "\n",
    "\n",
    "\\begin{equation}\n",
    "\\begin{array}\n",
    "\\\n",
    "d^{k-1}_j = \\beta_1 \\, d^{k-2}_j  + \\left(1 - \\beta_1\\right)\\frac{\\partial}{\\partial w_j}g\\left(\\mathbf{w}^{k-1}\\right) \\\\\n",
    "h_j^{k-1} = \\beta_2 \\, h_j^{k-2} + \\left(1 - \\beta_2\\right)\\left(\\frac{\\partial}{\\partial w_j}g\\left(\\mathbf{w}^{k-1}\\right)\\right)^2\n",
    "\\end{array}\n",
    "\\end{equation}\n",
    "\n",
    "where $\\beta_1$ and $\\beta_2$ lie in the range $[0,1]$.  Popular values the parameters of this update step are $\\beta_1 = 0.9$, $\\beta_2 = 0.999$.\n",
    "\n",
    "Note as with any exponential average these two updates apply when $k-1  > 0$ and should be initialized at first values from the series they respectively model: that is the initial descent direction $d^0_j = \\frac{\\partial}{\\partial w_j}g\\left(\\mathbf{w}^{0}\\right)$ and its squared magnitude $h^0_j = \\left(\\frac{\\partial}{\\partial w_j}g\\left(\\mathbf{w}^{0}\\right)\\right)^2$"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "9odbtq8NLhTr"
   },
   "source": [
    "The *Adam* step is then a component-normalized descent step using this exponentially average descent direction and magnitude.  A step in the $j^{th}$ coordinate then takes the form\n",
    "\n",
    "\\begin{equation}\n",
    "w_j^k = w_j^{k-1} - \\alpha \\frac{d^{k-1}_j}{\\sqrt{h_j^{k-1}}}.\n",
    "\\end{equation}\n",
    "\n",
    "where in practice of course a small $\\epsilon > 0$ (like e.g., $\\epsilon = 10^{-8}$) is added to the denominator to avoid division by zero.  "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "yquOU8TKLhTs"
   },
   "source": [
    "Notice - as we saw the (component) normalized step in the previous Section - that if we slightly re-write above as\n",
    "\n",
    "\\begin{equation}\n",
    "w_j^k = w_j^{k-1} - \\frac{\\alpha}{\\sqrt{h_j^{k-1}}} \\, d^{k-1}_j.\n",
    "\\end{equation}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "MmKvRH91LhTs"
   },
   "source": [
    "we can interpret the Adam step as a momentum-accelerated gradient descent step with an individual steplength / learning rate value $\\frac{\\alpha}{\\sqrt{h_j^{k-1}}}$ per component that all *adjusts themselves individually at each step based on component-wise exponentially normalized magnitude of the gradient*."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "4oDP29TeLhTs"
   },
   "source": [
    "--- "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "eZyl7ehhLhTs"
   },
   "source": [
    "Note: the authors of this particular update step proposed that each exponential average be *inialized at zero* - i.e., as \n",
    "$d^0_j = 0$ and $h^0_j = 0$ - instead of the first step in each series they respectively model (i.e., the initial derivative and its squared magnitude).  This initialization - along with the values for $\\beta_1$ and $\\beta_2$ typically chosen to be greater than $0.9$ - cause the the first few update steps of these exponential averages to be 'biased' towards zero as well.  Because of this they also employ a 'bias-correction' term to compensate for this initialization of the form $d^{k-1}_j \\longleftarrow \\frac{d^{k-1}_j }{1-\\left(\\beta_1\\right)^{k-1}}$ and $h^{k-1}_j \\longleftarrow \\frac{h^{k-1}_j }{1-\\left(\\beta_2\\right)^{k-1}}$."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "1ZqK99efLhTt"
   },
   "source": [
    "---"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "plYQvmsyLhTt"
   },
   "source": [
    "#### <span style=\"color:#a50e3e;\">Example 2.</span> Root Mean Squared Propogation (RMSprop)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "gdTELupOLhTt"
   },
   "source": [
    "This popular first order step is a varient of the *component-normalized* step discussed in [Section 3.9](https://jermwatt.github.io/machine_learning_refined/notes/3_First_order_methods/3_9_Normalized.html), where each component of the gradient is normalized by an *exponential average* of the magnitude of previously computed gradient descent directions.  \n",
    "\n",
    "In other words, we compute an exponential average of the *magnitude* of the gradient at each step.  Denoting $h_j^{k}$ the exponential average of of the squared magnitude of the $j^{th}$ partial derivative at step $k$ we have\n",
    "\n",
    "\\begin{equation}\n",
    "h_j^k = \\gamma \\, h_j^{k-1} + \\left(1 - \\gamma\\right)\\left(\\frac{\\partial}{\\partial w_j}g\\left(\\mathbf{w}^{k-1}\\right)\\right)^2\n",
    "\\end{equation}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "8j98KtIwLhTt"
   },
   "source": [
    "The Root Mean Squared Error Propogation (RMSprop) step is then a component-normalized descent step using this *exponential average*.  A step in the $j^{th}$ coordinate then takes the form\n",
    "\n",
    "\\begin{equation}\n",
    "w_j^k = w_j^{k-1} - \\alpha \\frac{\\frac{\\partial}{\\partial w_j} g\\left(\\mathbf{w}^{k-1}\\right)}{\\sqrt{h_j^{k-1}}}\n",
    "\\end{equation}\n",
    "\n",
    "where in practice of course a small $\\epsilon > 0$ (like e.g., $\\epsilon = 10^{-8}$) is added to the denominator to avoid division by zero.  Popular values the parameters of this update step are $\\beta = 0.9$ and $\\alpha = 10^{-2}$. "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "zhpc9zZLLhTt"
   },
   "source": [
    "Notice - as we saw the (component) normalized step in the previous Section - that if we slightly re-write above as\n",
    "\n",
    "\\begin{equation}\n",
    "w_j^k = w_j^{k-1} - \\frac{\\alpha}{\\sqrt{h_j^{k-1}}} \\, \\frac{\\partial}{\\partial w_j}g\\left(\\mathbf{w}^{k-1}\\right).\n",
    "\\end{equation}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "ue3YZTC3LhTu"
   },
   "source": [
    "we can interpret the RMSprop step as a standard gradient descent step with an individual steplength / learning rate value $\\frac{\\alpha}{\\sqrt{h_j^{k-1}}}$ per component that all *adjusts themselves individually at each step based on component-wise magnitude of the gradient*."
   ]
  }
 ],
 "metadata": {
  "colab": {
   "include_colab_link": true,
   "provenance": []
  },
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.8.10"
  },
  "toc": {
   "colors": {
    "hover_highlight": "#DAA520",
    "navigate_num": "#000000",
    "navigate_text": "#333333",
    "running_highlight": "#FF0000",
    "selected_highlight": "#FFD700",
    "sidebar_border": "#EEEEEE",
    "wrapper_background": "#FFFFFF"
   },
   "moveMenuLeft": true,
   "nav_menu": {
    "height": "103px",
    "width": "252px"
   },
   "navigate_menu": true,
   "number_sections": false,
   "sideBar": true,
   "threshold": 4,
   "toc_cell": false,
   "toc_section_display": "block",
   "toc_window_display": false,
   "widenNotebook": false
  }
 },
 "nbformat": 4,
 "nbformat_minor": 1
}
